Skip to content

Fix extension CLI teardown during AppHost build - #20021

Merged
Ella Hathaway (ellahathaway) merged 7 commits into
mainfrom
ellahathaway-fix-dynamic-debug-teardown
Sep 13, 2026
Merged

Ella Hathaway (ellahathaway) merged 7 commits into
mainfrom
ellahathaway-fix-dynamic-debug-teardown

Conversation

@ellahathaway

@ellahathaway Ella Hathaway (ellahathaway) commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Description

Stopping a dynamic debug session could terminate the Aspire CLI while a cold single-file AppHost build was still running. The extension's stopCli RPC used immediate process exit, bypassing normal command cancellation and child-process cleanup. On Windows, the orphaned dotnet build process retained the fixture workspace as its working directory, so E2E teardown failed with EBUSY while removing the second dynamic-debug workspace.

This change routes extension stop requests through the shared ConsoleCancellationManager, allowing the CLI's existing cancellation and process-tree cleanup paths to run. RunCommand also keeps pending pre-build work owned by the active command handler during manager-triggered shutdown. That is important because the cancellation manager's graceful deadline and final drain are the process-level escape hatch; returning from the handler after a separate local timeout could otherwise make shutdown appear complete before nested build cleanup finished.

Direct callers that cancel RunCommand with an unrelated token still use the bounded five-second wait. Those invocations do not have the cancellation manager's process-level deadline, so waiting indefinitely there could hang embedded or test callers.

The manager-owned cancellation regression requests the stop RPC synchronously from the fake project's RunAsync callback. This ensures the command enters its cleanup wait before fake time advances, rather than relying on Task.Yield() to schedule the handler. The pending run is released and awaited in finally, and the signaling time provider is shared with AppHostLauncherTests.

Validation:

  • 169 passed and two expected platform skips across RunCommandTests, AppHostLauncherTests, and ExtensionBackchannelTests (one Unix-only case and one case requiring Windows symlink privileges).
  • Negative control: temporarily restoring the five-second local timeout for manager-owned cancellation makes the focused regression fail at the timeout-policy assertion. The correct production code was restored and the affected classes were rerun successfully.
  • Earlier validation of the production fix: the Windows dynamic-debug-configuration shard passed three complete runs (9/9 tests), including validation with a temporary delayed AppHost build that reproduced the directory lock before the fix. The follow-up changes only refine test coverage and share its helper.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No

Route extension stop requests through cooperative cancellation and wait for in-flight pre-build cleanup before the CLI exits.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 20021

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 20021"

@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The five-second cleanup timeout can still let a pending build outlive the CLI.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Routes extension stop requests through cooperative CLI cancellation and waits for AppHost build cleanup.

Changes:

  • Replaces immediate CLI termination with cancellation-manager signaling.
  • Adds pending build cleanup handling and regression tests.
File summaries
File Description
src/Aspire.Cli/Backchannel/ExtensionRpcTarget.cs Cooperatively cancels CLI execution.
src/Aspire.Cli/Commands/RunCommand.cs Waits for canceled pre-build work.
tests/Aspire.Cli.Tests/Backchannel/ExtensionBackchannelTests.cs Tests stop-request cancellation.
tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs Tests cleanup ordering.
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Supplies the cancellation manager.
Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 1
  • Review effort level: Balanced

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/Aspire.Cli/Commands/RunCommand.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The cancellation lifecycle is correctly bounded and covered by focused regression tests.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Consolidate startup and pre-build cancellation into one helper with an explicit wait budget. Keep manager-owned shutdown under the central deadline, preserve bounded direct cancellation, and retain late task fault observation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The shutdown paths are correctly distinguished and covered by focused regression tests.

Review details
  • Files reviewed: 5/5 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Cancel synchronously at the fake project boundary and share the signaling time provider between command tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Comment thread src/Aspire.Cli/Commands/RunCommand.cs

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One lifecycle issue needs to be addressed before merge: extension cancellation after build completion can still let RunCommand finish before the pending AppHost run task has cleaned up. I confirmed it with a focused negative-control test and verified the minimal gate removal against the existing cancellation tests.

Proof: the PR's focused CLI classes pass (169 passed, 2 expected platform skips). The added post-build negative control fails on the PR head, then it and the three related cancellation tests pass after removing only the buildWaitCompleted gate. I did not rerun the full VS Code dynamic-debug E2E shard; that remains the real-path proof gap.

Comment thread src/Aspire.Cli/Commands/RunCommand.cs Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Cancellation during the startup-timeout cleanup wait can still allow the CLI to exit before child-process cleanup completes.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/RunCommand.cs
Propagate cancellation during timeout cleanup and centralize draining in finally so linked-token cancellation and concurrent RPC failures cannot bypass it.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The final cleanup decision has a race that can still let manager-triggered cancellation complete before the pending AppHost run is drained.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 7/7 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread src/Aspire.Cli/Commands/RunCommand.cs Outdated
@github-actions

This comment has been minimized.

Fence cancellation forwarding before the final drain decision, preserve teardown failure cleanup, and upgrade earlier bounded waits for a late manager stop.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector

2 / 99 PR test projects · 4 PR jobs · 1 advisory-only target, from 8 changed files.

Selected PR test projects (2 / 99)

Aspire.Cli.EndToEnd.Tests, Aspire.Cli.Tests

Selected PR jobs (4)

cli-starter-validation, extension-e2e, polyglot, typescript-api-compat

Advisory workflow impact (1)

  • deployment-e2e (schedule/dispatch-only)

How these were chosen — grouped by what changed

📦 affected project Aspire.Cli
1 test: Aspire.Cli.EndToEnd.Tests

🧪 tests/Aspire.Cli.Tests/Backchannel/ExtensionBackchannelTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Commands/AppHostLauncherTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/TestServices/CallbackConfigurationProvider.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/TestServices/SignalingFakeTimeProvider.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs (changed test)
1 directly: Aspire.Cli.Tests

Job reasons

Job Triggered by
cli-starter-validation affected project Aspire.Cli
deployment-e2e affected project Aspire.Cli
extension-e2e src/Aspire.Cli/Backchannel/ExtensionRpcTarget.cs, src/Aspire.Cli/Commands/RunCommand.cs, tests/Aspire.Cli.Tests/Backchannel/ExtensionBackchannelTests.cs, tests/Aspire.Cli.Tests/Commands/AppHostLauncherTests.cs, tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs, tests/Aspire.Cli.Tests/TestServices/CallbackConfigurationProvider.cs, tests/Aspire.Cli.Tests/TestServices/SignalingFakeTimeProvider.cs, tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs
• affected project Aspire.Cli
polyglot affected project Aspire.Cli
typescript-api-compat affected project Aspire.Cli

Selection computed for commit eb77abd.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The shutdown lifecycle changes are consistently wired and covered by focused deterministic regression tests.

Review details
  • Files reviewed: 8/8 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the CLI cancellation and teardown changes. No issues found.

@ellahathaway
Ella Hathaway (ellahathaway) merged commit 00d3e58 into main Sep 13, 2026
347 of 350 checks passed
@ellahathaway
Ella Hathaway (ellahathaway) deleted the ellahathaway-fix-dynamic-debug-teardown branch September 13, 2026 21:20
@github-actions github-actions Bot added this to the 13.6 milestone Sep 13, 2026
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

✅ No documentation update needed.

Step 5 branch taken: docs_required → already documented by name

Triggered signals (1): cli_command_file_changed — evidence: src/Aspire.Cli/Commands/RunCommand.cs matched the CLI-command-file path pattern (any change to an existing *Command.cs file trips this signal; it is not tied to a new flag, option, or API).

Why no docs change is needed:

This PR is an internal reliability bug fix, not a new user-facing feature or behavior change. Previously, the VS Code extension's stopCli RPC called immediate process exit, bypassing the CLI's normal cancellation/cleanup path, which could orphan a dotnet build process on Windows and cause EBUSY failures. The fix routes extension-initiated stop requests through the same ConsoleCancellationManager cooperative-cancellation → graceful-wait → force-kill sequence that aspire run already uses for Ctrl+C/SIGTERM, and keeps pending pre-build work owned by the active command handler during that shutdown so nested build cleanup can finish before the process exits. No new CLI option, command, environment variable, or public API was introduced (confirmed via the PR's own checklist: "Did you add public API? No"), and no user-visible input/output changes (flags, messages, defaults) were added.

Docs already describe this behavior by name, in src/frontend/src/content/docs/reference/cli/commands/aspire-run.mdx under "## Stopping the AppHost":

  1. Cooperative cancellation — The CLI requests that the AppHost stop gracefully.
  2. Graceful wait — The CLI waits for the AppHost process to exit cleanly on its own.
  3. Automatic force-kill — If the AppHost does not exit within the graceful timeout, the CLI terminates the process automatically.

The fix simply ensures the VS Code extension's stop request now goes through this already-documented three-step sequence instead of bypassing it — the observable, documented end-user behavior of aspire run's shutdown is unchanged. No identifiable new

(summary truncated)

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ CI Failure Analysis: Possible Flaky Test(s)

The CI build failed due to test failure(s) that appear unrelated to the PR changes. These may be flaky tests.

Suspected flaky test(s):

Suggested actions:

  • Re-run the failed CI jobs to confirm if the failure is intermittent
  • If the test continues to fail, consider quarantining it using /quarantine-test <test name> <issue URL>
  • Search existing issues to see if this test is already known to be flaky

You can re-run the failed jobs from the workflow run page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants